package com.ansion.commonchat.startup.workmanager.bwork.utils;


import static com.ansion.commonchat.startup.workmanager.bwork.BWorkInfo.State.BLOCKED;
import static com.ansion.commonchat.startup.workmanager.bwork.BWorkInfo.State.CANCELLED;
import static com.ansion.commonchat.startup.workmanager.bwork.BWorkInfo.State.ENQUEUED;
import static com.ansion.commonchat.startup.workmanager.bwork.BWorkInfo.State.FAILED;
import static com.ansion.commonchat.startup.workmanager.bwork.BWorkInfo.State.SUCCEEDED;

import android.text.TextUtils;

import androidx.annotation.NonNull;

import com.ansion.commonchat.startup.workmanager.bwork.BOperation;
import com.ansion.commonchat.startup.workmanager.bwork.BWorkInfo;
import com.ansion.commonchat.startup.workmanager.bwork.BWorkRequest;
import com.ansion.commonchat.startup.workmanager.bwork.impl.BOperationImpl;
import com.ansion.commonchat.startup.workmanager.bwork.impl.BSchedulers;
import com.ansion.commonchat.startup.workmanager.bwork.impl.BWorkContinuationImpl;
import com.ansion.commonchat.startup.workmanager.bwork.impl.BWorkManagerImpl;
import com.ansion.commonchat.startup.workmanager.bwork.impl.mode.BDependency;
import com.ansion.commonchat.startup.workmanager.bwork.impl.mode.BWorkCallback;
import com.ansion.commonchat.startup.workmanager.bwork.impl.mode.BWorkSpec;
import com.ansion.commonchat.startup.workmanager.bwork.impl.mode.BWorkTag;
import com.ansion.commonchat.startup.workmanager.bwork.impl.mode.WorkDataCache;
import com.ansion.exception.ExceptionExtension;
import com.ansion.log.LogTag;
import com.ansion.log.VLog;

import java.util.List;
import java.util.Set;


/**
 * Created by wanghongjie on 2023/5/10
 * Describe : 用于将任务添加到工作队列中。
 * <p>
 * 负责将任务链提交给 WorkManagerImpl 进行调度和执行，并处理调度过程中的异常情况。
 * <p>
 * 当使用 WorkManager 构建任务时，需要通过调用 enqueue() 方法将任务添加到工作队列中以进行调度和执行。
 * EnqueueRunnable 类封装了这个过程，它实现了 Runnable 接口，可以在后台线程中执行。
 * <p>
 * 主要功能包括：
 * 1.获取 WorkManagerImpl 实例，它是 Work Manager 的主要入口点，用于执行任务的调度和管理。
 * 2.获取 WorkContinuationImpl 实例，它表示一个任务链或工作流，包含一个或多个任务及其依赖关系。
 * 3.调用 WorkContinuationImpl.enqueue() 方法将任务链添加到工作队列中进行调度。
 * 4.处理调度过程中的异常情况，并通过日志记录相关信息。
 * 5.EnqueueRunnable 类的执行是在后台线程中进行的，它负责将构建好的任务链提交给 WorkManagerImpl 进行调度和执行。
 * 通过将任务添加到工作队列，Work Manager 可以根据任务的约束条件、调度策略和设备状态等因素进行灵活的任务调度，以最佳方式执行任务。
 */
public class BEnqueueRunnable implements Runnable {

    private final BWorkContinuationImpl mWorkContinuation;
    private final BOperationImpl mOperation;

    public BEnqueueRunnable(@NonNull BWorkContinuationImpl workContinuation) {
        mWorkContinuation = workContinuation;
        mOperation = new BOperationImpl();
    }

    @Override
    public void run() {
        try {
            if (mWorkContinuation.hasCycles()) {
                ExceptionExtension.throwThrowable(new IllegalStateException(
                        String.format("WorkContinuation has cycles (%s)", mWorkContinuation)));
            }
            // 在此处将数据存入缓存,并且判断任务（或任务链）是否需要调度。
            boolean needsScheduling = addToDataCache();
            if (needsScheduling) {
                scheduleWorkInBackground();
            }
            mOperation.setState(BOperation.SUCCESS);
        } catch (Throwable exception) {
            mOperation.setState(new BOperation.State.FAILURE(exception));
        }
    }

    @NonNull
    public BOperation getOperation() {
        return mOperation;
    }

    public boolean addToDataCache() {
        return processContinuation(mWorkContinuation);
    }

    public void scheduleWorkInBackground() {
        BWorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();
        BSchedulers.schedule(workManager.getWorkDataCache(), workManager.getSchedulers());
    }

    private static boolean processContinuation(@NonNull BWorkContinuationImpl workContinuation) {
        boolean needsScheduling = false;
        List<BWorkContinuationImpl> parents = workContinuation.getParents();
        if (parents != null) {
            for (BWorkContinuationImpl parent : parents) {
                if (!parent.isEnqueued()) {
                    needsScheduling |= processContinuation(parent);
                } else {
                    if (VLog.OPEN_LOG)
                        VLog.w(LogTag.TAG_B_WORK_MANAGER, "Already enqueued work ids (%s).", TextUtils.join(", ", parent.getIds()));
                }
            }
        }
        needsScheduling |= enqueueContinuation(workContinuation);
        return needsScheduling;
    }

    private static boolean enqueueContinuation(@NonNull BWorkContinuationImpl workContinuation) {
        Set<String> prerequisiteIds = BWorkContinuationImpl.prerequisitesFor(workContinuation);

        boolean needsScheduling = enqueueWorkWithPrerequisites(
                workContinuation.getWorkManagerImpl(),
                workContinuation.getWork(),
                prerequisiteIds.toArray(new String[0]));

        workContinuation.markEnqueued();
        return needsScheduling;
    }

    private static boolean enqueueWorkWithPrerequisites(
            BWorkManagerImpl workManagerImpl,
            @NonNull List<? extends BWorkRequest> workList,
            String[] prerequisiteIds) {

        boolean needsScheduling = false;

        WorkDataCache workDataCache = workManagerImpl.getWorkDataCache();

        boolean hasPrerequisite = (prerequisiteIds != null && prerequisiteIds.length > 0);
        boolean hasCompletedAllPrerequisites = true;
        boolean hasFailedPrerequisites = false;
        boolean hasCancelledPrerequisites = false;

        if (hasPrerequisite) {
            for (String id : prerequisiteIds) {
                BWorkSpec prerequisiteWorkSpec = workDataCache.workSpecDao().getWorkSpec(id);
                if (prerequisiteWorkSpec == null) {
                    if (VLog.OPEN_LOG)
                        VLog.e(LogTag.TAG_B_WORK_MANAGER, "Prerequisite %s doesn't exist; not enqueuing", id);
                    return false;
                }
                BWorkInfo.State prerequisiteState = prerequisiteWorkSpec.state;
                hasCompletedAllPrerequisites &= (prerequisiteState == SUCCEEDED);
                if (prerequisiteState == FAILED) {
                    hasFailedPrerequisites = true;
                } else if (prerequisiteState == CANCELLED) {
                    hasCancelledPrerequisites = true;
                }
            }
        }

        for (BWorkRequest work : workList) {
            BWorkSpec workSpec = work.getWorkSpec();

            if (hasPrerequisite && !hasCompletedAllPrerequisites) {
                if (hasFailedPrerequisites) {
                    workSpec.state = FAILED;
                } else if (hasCancelledPrerequisites) {
                    workSpec.state = CANCELLED;
                } else {
                    workSpec.state = BLOCKED;
                }
            }

            if (workSpec.state == ENQUEUED) {
                needsScheduling = true;
            }

            workDataCache.workSpecDao().insertWorkSpec(workSpec);

            if (hasPrerequisite) {
                for (String prerequisiteId : prerequisiteIds) {
                    BDependency dep = new BDependency(work.getStringId(), prerequisiteId);
                    workDataCache.dependencyDao().insertDependency(dep);
                }
            }

            for (String tag : work.getTags()) {
                workDataCache.workTagDao().insert(new BWorkTag(tag, work.getStringId()));
            }

            /*
             * 监听处理规则：
             * 若 getWorkListener 不为null ，且 设置了批量的ids，则为批量监听。
             * 若 getWorkListener 不为null ，且 未设置批量ids，则默认监听当前work。
             * 若 getWorkListener 为 null，则不监听。
             */
            if (VLog.OPEN_LOG)
                VLog.d(LogTag.TAG_B_WORK_MANAGER, " BEnqueueRunnable BWorkRequest getWorkListener is null ??? , %s ", work.getWorkListener() == null);
            if (work.getWorkListener() != null) {
                BWorkCallback callback;
                if (work.getBatchWorkIds() == null || work.getBatchWorkIds().isEmpty()) {
                    callback = new BWorkCallback(work.getStringId(), work.getWorkListener());
                } else {
                    callback = new BWorkCallback(work.getBatchWorkIds(), work.getWorkListener());
                }
                workDataCache.workCallbackDao().insertWorkCallback(callback);
            }
        }
        return needsScheduling;
    }
}